package iammert.com.expandablelib;

import ohos.agp.components.*;

import ohos.app.Context;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by mertsimsek on 28/07/2017.
 */
public class ExpandableLayout extends DirectionalLayout {
    /**
     * Renderer
     *
     * @param <P> P
     * @param <C> C
     */
    public interface Renderer<P, C> {
        /**
         * renderParent
         *
         * @param view           Component
         * @param model          P
         * @param isExpanded     boolean
         * @param parentPosition int
         */
        void renderParent(Component view, P model, boolean isExpanded, int parentPosition);

        /**
         * renderChild
         *
         * @param view           Component
         * @param model          C
         * @param parentPosition int
         * @param childPosition  int
         */
        void renderChild(Component view, C model, int parentPosition, int childPosition);
    }

    private static final int NO_RES = 0;

    private static final int NO_INDEX = -1;

    private static final Operation DEFAULT_FILTER = new Operation() {
        @Override
        public boolean apply(Object obj) {
            return true;
        }
    };

    private LayoutScatter layoutInflater;

    private int parentLayout;

    private int childLayout;

    private Renderer renderer;

    private List<Section> sections;

    private ExpandCollapseListener.ExpandListener expandListener;

    private ExpandCollapseListener.CollapseListener collapseListener;

    private Operation currentFilter = DEFAULT_FILTER;

    /**
     * ExpandableLayout
     *
     * @param context Context
     */
    public ExpandableLayout(Context context) {
        super(context);
        init(context);
    }

    /**
     * ExpandableLayout
     *
     * @param context Context
     * @param attrs   AttrSet
     */
    public ExpandableLayout(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * ExpandableLayout
     *
     * @param context   Context
     * @param attrs     AttrSet
     * @param styleName String
     */
    public ExpandableLayout(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init(context);
    }

    /**
     * <declare-styleable name="ExpandableLayout">
     * <attr name="parentLayout" format="integer" />
     * <attr name="childLayout" format="integer" />
     * </declare-styleable>
     *
     * @param context Context
     */
    private void init(Context context) {
        setOrientation(VERTICAL);
        sections = new ArrayList<>();
        layoutInflater = LayoutScatter.getInstance(context);
        parentLayout = NO_RES;
        childLayout = NO_RES;
    }

    /**
     * setChildLayout
     *
     * @param childLayout int
     */
    public void setChildLayout(int childLayout) {
        this.childLayout = childLayout;
        invalidate();
    }

    /**
     * setParentLayout
     *
     * @param parentLayout int
     */
    public void setParentLayout(int parentLayout) {
        this.parentLayout = parentLayout;
        invalidate();
    }

    /**
     * setExpandListener
     *
     * @param expandListener ExpandCollapseListener
     * @param <P>            P
     */
    public <P> void setExpandListener(ExpandCollapseListener.ExpandListener<P> expandListener) {
        this.expandListener = expandListener;
    }

    /**
     * setCollapseListener
     *
     * @param collapseListener ExpandCollapseListener.CollapseListener<P>
     * @param <P>              P
     */
    public <P> void setCollapseListener(ExpandCollapseListener.CollapseListener<P> collapseListener) {
        this.collapseListener = collapseListener;
    }

    /**
     * setRenderer
     *
     * @param renderer Renderer
     */
    public void setRenderer(Renderer renderer) {
        this.renderer = renderer;
    }

    /**
     * addSection
     *
     * @param section Section
     */
    public void addSection(Section section) {
        sections.add(section);
        notifySectionAdded(section);
    }

    /**
     * getSections
     *
     * @return List<Section>
     */
    public List<Section> getSections() {
        return sections;
    }

    /**
     * addChild
     *
     * @param parent P
     * @param child  C
     * @param <P>    P
     * @param <C>    C
     */
    public <P, C> void addChild(P parent, C child) {
        int parentIndex = NO_INDEX;
        for (int i = 0; i < sections.size(); i++) {
            if (sections.get(i).getParent().equals(parent)) {
                if (!sections.get(i).getChildren().contains(child)) {
                    sections.get(i).addChildren(child);
                }
                parentIndex = i;
            }
        }
        if (parentIndex != NO_INDEX) {
            notifyItemAdded(parentIndex, child);
            if (sections.get(parentIndex).isExpanded()) {
                expand(parent);
            }
        }
    }

    /**
     * addChildren
     *
     * @param parent   p
     * @param children List<C> children
     * @param <P>      P
     * @param <C>      C
     */
    public <P, C> void addChildren(P parent, List<C> children) {
        int parentIndex = NO_INDEX;
        for (int i = 0; i < sections.size(); i++) {
            if (sections.get(i).getParent().equals(parent)) {
                if (!sections.get(i).getChildren().containsAll(children)) {
                    sections.get(i).getChildren().addAll(children);
                }
                parentIndex = i;
            }
        }
        if (parentIndex != NO_INDEX) {
            notifyItemAdded(parentIndex, children);
            if (sections.get(parentIndex).isExpanded()) {
                expand(parent);
            }
        }
    }

    /**
     * filterParent
     *
     * @param op Operation
     */
    public void filterParent(Operation op) {
        for (Section section : getSections()) {
            final Object parent = section.getParent();
            final boolean contains = op.apply(parent);
            getComponentAt(sections.indexOf(section)).setVisibility(contains ? Component.VISIBLE : Component.HIDE);
        }
    }

    /**
     * filterChildren
     *
     * @param op Operation
     */
    public void filterChildren(Operation op) {
        currentFilter = op;
        for (int i = 0; i < getSections().size(); i++) {
            final List children = getSections().get(i).getChildren();
            getSections().get(i).setExpanded(true);
            boolean keepParentVisible = false;
            final ComponentContainer childrenViews = (ComponentContainer) getComponentAt(sections.indexOf(getSections().get(i)));
            for (int j = 0; j < children.size(); j++) {
                final Object child = children.get(j);
                final boolean contains = op.apply(child);
                childrenViews.getComponentAt(children.indexOf(child) + 1).setVisibility(contains ? Component.VISIBLE : Component.HIDE);
                if (!keepParentVisible && contains) {
                    keepParentVisible = true;
                }
            }
            childrenViews.setVisibility(keepParentVisible ? Component.VISIBLE : Component.HIDE);
            if (!ObjectUtils.isNull(expandListener)){
                expandListener.onExpanded(i, getSections().get(i).getParent(), ((ComponentContainer) getComponentAt(i)).getComponentAt(0));
            }
        }
    }

    private <C> void notifyItemAdded(int parentIndex, C child) {
        if (ObjectUtils.isNull(renderer)) {
            return;
        }
        ComponentContainer parentView = (ComponentContainer) getComponentAt(parentIndex);
        Component childView = layoutInflater.parse(childLayout, null, false);
        renderer.renderChild(childView, child, parentIndex, sections.get(parentIndex).getChildren().size() - 1);
        parentView.addComponent(childView);
    }

    private <C> void notifyItemAdded(int parentIndex, List<C> children) {
        if (ObjectUtils.isNull(renderer)) {
            return;
        }
        ComponentContainer parentView = (ComponentContainer) getComponentAt(parentIndex);
        for (int i = 0; i < children.size(); i++) {
            Component childView = layoutInflater.parse(childLayout, null, false);
            renderer.renderChild(childView, children.get(i), parentIndex, i);
            parentView.addComponent(childView);
        }
    }

    private void notifySectionAdded(final Section section) {
        if (ObjectUtils.isNull(renderer)){
            return;
        }
        DirectionalLayout sectionLayout = new DirectionalLayout(getContext());
        sectionLayout.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT));
        sectionLayout.setOrientation(DirectionalLayout.VERTICAL);
        Component parentView = layoutInflater.parse(parentLayout, null, false);
        parentView.setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {
                if (section.isExpanded()) {
                    collapse(section.getParent());
                } else {
                    expand(section.getParent());
                }
            }
        });
        renderer.renderParent(parentView, section.getParent(), section.isExpanded(), sections.size() - 1);
        sectionLayout.addComponent(parentView);
        if (section.isExpanded()) {
            for (int i = 0; i < section.getChildren().size(); i++) {
                Object child = section.getChildren().get(i);
                Component childView = layoutInflater.parse(childLayout, null, false);
                renderer.renderChild(childView, child, sections.size() - 1, i);
                sectionLayout.addComponent(childView);
            }
        }
        addComponent(sectionLayout);
    }

    /**
     * notifyParentChanged
     *
     * @param position int
     */
    public void notifyParentChanged(int position) {
        if (position > getChildCount() - 1) {
            return;
        }
        ComponentContainer viewGroup = (ComponentContainer) getComponentAt(position);
        if (viewGroup != null && viewGroup.getChildCount() > 0) {
            Component parentView = viewGroup.getComponentAt(0);
            renderer.renderParent(parentView, sections.get(position).getParent(), sections.get(position).isExpanded(), position);
        }
    }

    private <P> void expand(P parent) {
        for (int i = 0; i < sections.size(); i++) {
            if (parent.equals(sections.get(i).getParent())) {
                ComponentContainer sectionView = ((ComponentContainer) getComponentAt(i));
                for (int j = 1; j < sectionView.getChildCount(); j++) {
                    final Component childView = sectionView.getComponentAt(j);
                    final Object childType = sections.get(i).getChildren().get(j - 1);
                    childView.setVisibility(currentFilter.apply(childType) ? Component.VISIBLE : Component.HIDE);
                }
                sections.get(i).setExpanded(true);
                if (!ObjectUtils.isNull(expandListener)){
                    expandListener.onExpanded(i, sections.get(i).getParent(), sectionView.getComponentAt(0));
                }
                break;
            }
        }
    }

    private <P> void collapse(P parent) {
        for (int i = 0; i < sections.size(); i++) {
            if (parent.equals(sections.get(i).getParent())) {
                ComponentContainer sectionView = ((ComponentContainer) getComponentAt(i));
                sections.get(i).setExpanded(false);
                for (int j = 1; j < sectionView.getChildCount(); j++) {
                    sectionView.getComponentAt(j).setVisibility(Component.HIDE);
                }
                if (!ObjectUtils.isNull(collapseListener)){
                    collapseListener.onCollapsed(i, sections.get(i).getParent(), sectionView.getComponentAt(0));
                }
                break;
            }
        }
    }
}
